package com.comcast.util;

import static java.lang.String.format;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;

import com.comcast.util.DaemonThreadFactory;
import com.comcast.util.Encryptor;

/**
 * Configuration component manages the properties loaded from multiple resources.
 *
 * <p>Now supports reloading both file and database properties every 10 minutes.
 * All the changes in file properties will be reloaded. But only reloadable
 * (PROP_RELOADABLE='Y') database properties will be reloaded.
 *
 * <p>Now supports database properties fallback mechanism. Database properties
 * will be backed up as properties files in the dbprops_fallback folder under
 * ${esp.propsDir} automatically and will be used to initialize the services
 * should the database becomes unavailable.
 *
 * @author Tao Huang
 * @author Charles Zhao
 *
 */
public class ConfigurationManagerImpl
    implements ConfigurationManager, InitializingBean, DisposableBean, ResourceLoaderAware  {
    private static Log log = LogFactory.getLog(ConfigurationManagerImpl.class);

    public final static String ESP_PROPDIR_SYSPROP_NAME = "esp.propDir";
    private final static String ESP_DATA_CENTER = "ESP.datacenter";
    private final static String DBPROPS_FALLBACK_FOLDER_NAME = "dbprops_fallback";
    private static final String PROPERTIES_FILE_EXTENSION = ".properties";
    /**
     * NTFS - 226 bytes
     * ext3 - 255 bytes
     * FAT32 - 255 bytes
     */
    private final static int MAX_DBPROPS_FALLBACK_FILENAME_LENGTH = 226 - PROPERTIES_FILE_EXTENSION.length();
    
    private String dbPropsFallbackFolder;

    private String dbPropsFallbackFilePath;

    private Resource[] locations;

    private List<DBComponentConfiguration> databaseConfigurations;

    //Stores the actual DatabaseConfiguration(s) to be merged with reloaded DatabaseConfiguration(s) later.
    private BaseConfiguration dbProperties;

    private List<ReloadableConfigurationListener> reloadableConfigurationListeners = new ArrayList<ReloadableConfigurationListener>();

    private DataSource dataSource;

    private boolean ignoreResourceNotFound = false;

    private boolean reloadDynamically = false;

    //Reload interval, in seconds. Default to 10 minutes.
    private int reloadIntervalInSeconds = 10 * 60;

    private ScheduledExecutorService reloadExecutor;

    private Configuration configuration;

    private Encryptor encryptor;

	private ResourceLoader resourceLoader;
	private String espDatacenter;

	//By pthakk200 : JIRA:TEAMT-445
	private Map<String, Long> resourceMap = new HashMap<String, Long>();
	
    public Encryptor getEncryptor() {
		return encryptor;
	}

	public void setEncryptor(Encryptor encryptor) {
		this.encryptor = encryptor;
	}

	public ConfigurationManagerImpl() {
    }

    /**
     * Set a location of a properties file to be loaded.
     * <p>Can point to a classic properties file.
     */
    public void setLocation(Resource location) {
        this.locations = new Resource[] {location};
    }

    public void setDatabaseConfigurations(List<DBComponentConfiguration> dbComponentConfigs) {
    	this.databaseConfigurations = dbComponentConfigs;
    }

    public void setDataSource(DataSource dataSource) {
    	this.dataSource = dataSource;
    }

    public List<DBComponentConfiguration> getDatabaseConfigurations() {
    	return this.databaseConfigurations;
    }

    /**
     * Set locations of properties files to be loaded.
     * <p>Can point to classic properties files.
     */
    public void setLocations(Resource[] locations) {
        this.locations = locations;
    }

    public void addReloadableConfigurationListener(ReloadableConfigurationListener listener) {
    	reloadableConfigurationListeners.add(listener);
    }

	public boolean isReloadDynamically() {
		return reloadDynamically;
	}

	public void setReloadDynamically(boolean reloadDynamically) {
		this.reloadDynamically = reloadDynamically;
	}

	public int getReloadIntervalInSeconds() {
		return reloadIntervalInSeconds;
	}

	public void setReloadIntervalInSeconds(int reloadIntervalInSeconds) {
		this.reloadIntervalInSeconds = reloadIntervalInSeconds;
	}

	private void load() {
        CompositeConfiguration config = new CompositeConfiguration();
        if (locations == null && this.databaseConfigurations == null) {
            this.configuration = config;
            return;
        }

        //By pthakk200 : JIRA:TEAMT-445
        // Commenting out the call to load database configurations, since the functionality has been deprecated.
        //loadDatabaseConfigurations(config);

        loadFileConfigurations(config);
        this.configuration = config;
    }

    /**
	 * <b>NOTE:</b> Reloads file properties.
	 *
     * @see com.comcast.core.framework.configuration.Configurable#reload()
     */
    public boolean reload() {
		if ( log.isInfoEnabled() ) {
			log.info("Trying to reload configurations...");
		}

        CompositeConfiguration newConfig = new CompositeConfiguration();
    	try {
    		
            // By pthakk200 : JIRA:TEAMT-445
            // Commenting out the call to load database configurations, since the functionality has been deprecated.
    		// reloadDatabaseConfigurations(newConfig);

    		loadFileConfigurations(newConfig);

    		if (newConfig.getKeys() != null && newConfig.getKeys().hasNext())
    		{
	    		Properties updatedProperties = diffConfigurations(newConfig, this.configuration);
	    		if ( updatedProperties.size() > 0 ) {
	    			this.configuration = mergeFileProperties(newConfig, this.configuration);
		    		for ( ReloadableConfigurationListener reloadableConfigurationListener : reloadableConfigurationListeners ) {
		    			reloadableConfigurationListener.propertiesReloaded(updatedProperties);
		    		}
		            
		    		// By pthakk200 : JIRA:TEAMT-445
		            // Commenting out the call to load database configurations, since the functionality has been deprecated.
		    		//backupReloadedDatabaseConfigurations(updatedProperties);

		    		if ( log.isInfoEnabled() ) {
		    			log.info("Detected configuration changes. Configurations were reloaded successfully.");
		    		}
		    		return true;
	    		} else {
		    		if ( log.isInfoEnabled() ) {
		    			log.info("No configuration change detected. Configurations were not reloaded.");
		    		}
	    		}
    		} else {
	    		if ( log.isInfoEnabled() ) {
	    			log.info("No configuration change detected. Configurations were not reloaded.");
	    		}
    		}
    	}
    	catch (Throwable e) {
    		log.warn("Could not reload configurations.", e);
    	}
    	return false;
    }

    /**
     * Merge the modified configurations with the already loaded configurations.
     * @param reloadedConfiguration
     * @param existingConfiguration
     * @return
     */
    private CompositeConfiguration mergeFileProperties(CompositeConfiguration reloadedConfiguration, Configuration existingConfiguration) {
    	if ( existingConfiguration == null ) {
    		return reloadedConfiguration;
    	}
    	
    	// We will return this configuration.
    	CompositeConfiguration mergedConfiguration = (CompositeConfiguration)((CompositeConfiguration)existingConfiguration).clone();

    	// Loop through the modified configurations and replace them in the to-be-returned configuration object.
    	Configuration modifiedConfiguration;
    	for (int i = 0; i < reloadedConfiguration.getNumberOfConfigurations(); i++) {
    		// Get the configurations.
    		modifiedConfiguration = reloadedConfiguration.getConfiguration(i);
    		Iterator<?> keys = modifiedConfiguration.getKeys();
    		// The configuration should have keys that should have been modified.
    		// If no keys exists, then there is no change to this configuration.
    		if ( keys.hasNext() ) {
    			// Get the first key
    			String key = (String)keys.next();
    			
    			// Check for existence of this key in the to-be-returned configuration object.
    			if(mergedConfiguration.containsKey(key)) {
    				// If the key exists, get the configuration holding this key and remove it from the 
    				// to-be-returned configuration object. That data is stale.
	    			Configuration c = getSource(key, mergedConfiguration);
	    			mergedConfiguration.removeConfiguration(c);
    			}
    			
    			// Add the modified configuration to the to-be-returned configuration object
	    		mergedConfiguration.addConfiguration(modifiedConfiguration);
    		}
		}
    	
    	//	Return the merged configuration. This will be the base configuration going forward.
    	return mergedConfiguration;
	}
    
	private void backupReloadedDatabaseConfigurations(Properties updatedProperties) {
		if ( dbProperties != null && isDatabaseConfigurationUpdated(dbProperties, updatedProperties) ) {
			backupDatabaseConfigurations(dbProperties);
		}
	}

    private boolean isDatabaseConfigurationUpdated(BaseConfiguration databaseConfiguration, Properties updatedProperties) {
    	Iterator<?> keys = databaseConfiguration.getKeys();
    	while ( keys.hasNext() ) {
    		String key = (String)keys.next();
    		if ( updatedProperties.containsKey(key) ) {
    			return true;
    		}
    	}
    	return false;
	}

	/**
     * Returns a set of property names that are updated.
     */
    private Properties diffConfigurations(CompositeConfiguration newConfig, Configuration oldConfig) {
    	Properties diffProps = new Properties();
    	Iterator<?> keys = newConfig.getKeys();
    	while (keys.hasNext() ) {
    		String key = (String)keys.next();
    		String newValue = getStringValue(newConfig, key);
    		String oldValue = getStringValue(oldConfig, key);
    		if ( newValue == null ? oldValue != null : !newValue.equals(oldValue) ) {
        		if ( log.isInfoEnabled() ) {
        			log.info(format("Detected change to property: '%s'. New value: '%s', old value: '%s' ", key, newValue, oldValue));
        		}
        		diffProps.setProperty(key, newValue);
    		}
    	}
		return diffProps;
	}

    private String getStringValue(Configuration config, String propertyName) {
    	Object property = config.getProperty(propertyName);
    	if ( property instanceof String ) {
    		return config.getString(propertyName);
    	}
    	if ( property instanceof List) {
    		return StringUtils.join(config.getStringArray(propertyName), ",");
    	}

		if ( log.isWarnEnabled() ) {
			log.warn(format("Could not get string value of property '%s'. The property (%s) is neither String nor List.", propertyName, property));
		}
    	return null;
    }

	public void afterPropertiesSet() {
    	Assert.isTrue(databaseConfigurations == null || (databaseConfigurations != null && dataSource != null), "Must specify a data source for database configurations.");
    	if ( databaseConfigurations != null ) {
    		Assert.isTrue(databaseConfigurations.size()>0, "Must specify at least one DB Component when databaseConfigurations is configured.");
    		String espPropDir = System.getProperty(ESP_PROPDIR_SYSPROP_NAME);
    		this.dbPropsFallbackFolder = (espPropDir == null ? "" : espPropDir) + File.separator + DBPROPS_FALLBACK_FOLDER_NAME;
    		this.dbPropsFallbackFilePath = 	getFallbackFilePath();
    	}

    	espDatacenter = System.getProperty(ESP_DATA_CENTER);    	
    	
    	load();
    	if ( this.reloadDynamically ) {
	    	reloadExecutor = Executors.newSingleThreadScheduledExecutor(DaemonThreadFactory.getInstance());
	    	reloadExecutor.scheduleWithFixedDelay(new Runnable() {
				public void run() {
					reload();
				}
	    	}, reloadIntervalInSeconds, reloadIntervalInSeconds, TimeUnit.SECONDS);
	    	if ( log.isInfoEnabled() ) {
	    		log.info(format("Started the executor that checks configuration changes every %d seconds", reloadIntervalInSeconds));
	    	}
    	}
    }


	public void destroy() throws Exception {
		if ( reloadExecutor != null ) {
			reloadExecutor.shutdownNow();
	    	if ( log.isInfoEnabled() ) {
	    		log.info(format("Stopped the executor that checks configuration changes every %d seconds", reloadIntervalInSeconds));
	    	}
		}
	}

    private void loadDatabaseConfigurations(CompositeConfiguration config) {
    	if (this.databaseConfigurations == null) return;

		if (log.isInfoEnabled()) {
            log.info("Loading properties from database: " + databaseConfigurations.toArray());
        }

        try {
            DatabaseConfiguration properties = new DatabaseConfiguration(dataSource, databaseConfigurations);
            backupDatabaseConfigurations(properties);
            decryptProperties(properties);
            config.addConfiguration(properties);
            this.dbProperties = properties;
        }
        catch (Exception ex) {
        	ex.printStackTrace();
            if (log.isWarnEnabled()) {
                log.warn("Could not load properties from database. Falling back to backed up properties file in the fallback folder.", ex);
            }
            this.dbProperties = loadFallbackFileConfiguration(config);
        }
    }

	/**
     * Reloads the properties from database and merges the newly loaded properties with
     * existing database properties.
     */
    private void reloadDatabaseConfigurations(CompositeConfiguration newConfig) {
    	if (this.databaseConfigurations == null) return;

		if (log.isInfoEnabled()) {
            log.info("Reloading properties from database: " + databaseConfigurations);
        }

        try {
            BaseConfiguration reloadedProperties = new DatabaseConfiguration(dataSource, databaseConfigurations, true);
            decryptProperties(reloadedProperties);
            BaseConfiguration mergedDbProperties = mergeDbProperties(reloadedProperties, dbProperties);
        	dbProperties = mergedDbProperties;
        	newConfig.addConfiguration(mergedDbProperties);
        } catch (Exception ex) {
            if (log.isWarnEnabled()) {
                log.warn(format("Could not reload properties from database for %s. Ignored.", databaseConfigurations), ex);
            }
        }
    }

    private BaseConfiguration mergeDbProperties(BaseConfiguration reloadedProperties,BaseConfiguration existingDbProperties) {
    	if ( existingDbProperties == null ) {
    		return reloadedProperties;
    	}
    	
    	BaseConfiguration mergedConfiguration = (BaseConfiguration)existingDbProperties.clone();

    	Iterator<?> keys = reloadedProperties.getKeys();
    	while ( keys.hasNext() ) {
    		String key = (String)keys.next();
    		boolean delimiterParsingDisabled = mergedConfiguration.isDelimiterParsingDisabled();
    		mergedConfiguration.setDelimiterParsingDisabled(true);
    		mergedConfiguration.setProperty(key, reloadedProperties.getProperty(key));
    		mergedConfiguration.setDelimiterParsingDisabled(delimiterParsingDisabled);
    	}

    	return mergedConfiguration;
	}

    private void loadFileConfigurations(CompositeConfiguration config) {
    	if (locations == null)
    		return;

        for (Resource location : locations) {
        	if(location instanceof ClassPathResource) {
        		if (! resourceMap.containsKey(location.getFilename())) {
        			// This is a first time load of the classpath resource.
            		loadFileConfiguration(location, config);
    			    resourceMap.put(location.getFilename(), 1000l);
        		} else {
    				if (log.isInfoEnabled())
    					log.info("Classpath Configuration : " + location.getFilename() + " has not been modified since last load. Skipping this configuration.");
        		}
        	} 
        	else 
        	{
	        	File tmpFile = null;
				try {
					tmpFile = location.getFile();
				} catch (IOException e) {
					if(log.isDebugEnabled())
						log.debug(" Resource is not an externalized file. Exception message : " + e.getMessage());
					// Will attempt to load it anyways.
	        		loadFileConfiguration(location, config);
	        		continue;
				}
				
				// Check if the file has been modified since last load.
	        	if(loadFile(tmpFile)) {
	        		// File has been modified.  The configuration will be reloaded.
			    	long lastModified = tmpFile.lastModified();
	        		loadFileConfiguration(location, config);
				    resourceMap.put(tmpFile.getName(), lastModified);
	        	}
        	}
        }
    }

	private BaseConfiguration loadFileConfiguration(Resource location, CompositeConfiguration config) {
		if (log.isInfoEnabled()) {
		    log.info("Loading properties file from " + location);
		}

		if (!location.getFilename().endsWith(PROPERTIES_FILE_EXTENSION)) {
		    throw new RuntimeException("Could not handle property file: " + location);
		}

		try {
		    URL url = location.getURL();
		    PropertiesConfiguration properties = new PropertiesConfiguration(url);
		    decryptProperties(properties);
		    config.addConfiguration(properties);
		    return properties;
		}
		catch (Exception ex) {
		    if (ignoreResourceNotFound) {
		        if (log.isWarnEnabled()) {
		            log.warn("Could not load properties from " + location + ": " + ex.getMessage());
		        }
		        return null;
		    }
		    else {
		        throw new RuntimeException(ex);
		    }
		}
	}


    /**
     * Loads database properties from backed up properties file in the fallback folder.
     */
    private BaseConfiguration loadFallbackFileConfiguration(CompositeConfiguration config) {
    	Resource dbPropsFallbackFileResource = getFallbackFileResource();
    	if ( dbPropsFallbackFileResource.exists() ) {
    		return loadFileConfiguration(dbPropsFallbackFileResource, config);
    	} else {
    		throw new RuntimeException(format("Could not find fallback properties file for '%s'. Failed to load database properties.", databaseConfigurations));
    	}
	}


	/**
	 * Backs up database properties to properties file in the fallback folder.
	 */
	private void backupDatabaseConfigurations(BaseConfiguration properties) {
		createDbPropsFallbackFolderIfNeeded();
		File fallbackFileLock = new File(this.dbPropsFallbackFilePath + ".lck");
		boolean locked = false;
		try {
			locked = fallbackFileLock.createNewFile();
			if ( locked )  {
				storePropertiesToFile(properties, this.dbPropsFallbackFilePath);
				if ( log.isInfoEnabled() ) {
					log.info(format("Backed up database properties for '%s' in '%s' successfully.", databaseConfigurations, this.dbPropsFallbackFilePath));
				}
			} else {
				if ( log.isInfoEnabled() ) {
					log.info(format("Database properties for '%s' is being backed up by another peer. Leaving it to the peer.", databaseConfigurations));
				}
			}
		} catch (Exception e) {
			if ( log.isWarnEnabled() ) {
				log.warn(format("Could not back up database properties for '%s'. Will not be able to fall back with latest configurations if the database is down.", databaseConfigurations), e);
			}
		} finally {
			if ( locked ) {
				if ( !fallbackFileLock.delete() ) {
					if ( log.isWarnEnabled() ) {
						log.warn(format("Could not delete the lock file %s", fallbackFileLock.getAbsolutePath()));
					}
				}
			}
		}
	}

	private void storePropertiesToFile(BaseConfiguration properties, String fallbackFilePath) throws IOException {
		FileWriter writer = null;
		try {
			writer = new FileWriter(fallbackFilePath);
			Iterator<?> names = properties.getKeys();
			writer.write("#DATABASE PROPERTIES BACKUP. DO NOT EDIT THIS FILE!!!\n");
			while ( names.hasNext() ) {
				String name = (String)names.next();
				String value = getStringValue(properties, name);
				writer.write(name.replaceAll(" ", "\\\\ ") + "=" + value);
				writer.write("\n");
			}
		} finally {
			if ( writer != null ) {
					writer.close();
			}
		}
	}

	private Resource getFallbackFileResource() {
    	return this.resourceLoader.getResource("file:" + getFallbackFilePath());
    }

	private String getFallbackFilePath() {
		StringBuilder result = new StringBuilder();
		result.append(dbPropsFallbackFolder).append(File.separator);
		for ( int i = 0; i < databaseConfigurations.size(); i++ ) {
			String addToName = (i > 0 ? "-" : "" ) + databaseConfigurations.get(i).getName() + "-" + databaseConfigurations.get(i).getVersion();
			
			//Ensures that the length of the file name won't exceed the limit.
			if ( addToName.length() + result.length() < MAX_DBPROPS_FALLBACK_FILENAME_LENGTH ) {
				result.append(addToName);
			} else {
				break;
			}
		}
		result.append(PROPERTIES_FILE_EXTENSION);
		return result.toString();
	}

    private void createDbPropsFallbackFolderIfNeeded() {
    	File file = new File(this.dbPropsFallbackFolder);
    	if ( !file.exists() ) {
    		boolean succeeded = file.mkdirs();
    		if ( succeeded ) {
	    		if ( log.isInfoEnabled() ) {
	    			log.info(format("Created database properties fallback folder '%s' successfully.", this.dbPropsFallbackFolder));
	    		}
    		} else {
    			if ( log.isWarnEnabled() ) {
    				log.warn(format("Failed to create database properties fallback folder '%s'.", this.dbPropsFallbackFolder));
    			}
    		}
    	}
	}
    private void decryptProperties(Configuration properties) {
    	if ( encryptor != null ) {
    		Map<String, Object> decryptedProperties = new HashMap<String, Object>();
    		for ( Iterator<?> iter = properties.getKeys(); iter.hasNext(); ) {
    			String key = (String)iter.next();
    			Object value = properties.getProperty(key);
    			if ( value instanceof String) {
    				if ( encryptor.isEncrypted((String)value)) {
    					decryptedProperties.put(key, encryptor.decrypt((String)value));
    				}
    			} else if ( value instanceof List) {
    				List values = (List)value;
    				for ( int i = 0; i < values.size(); i++) {
    					if ( encryptor.isEncrypted((String)values.get(i)) ) {
    						values.set(i, encryptor.decrypt((String)values.get(i)));
    					}
    				}
    			}
    		}
    		for(Entry<String, Object> entry : decryptedProperties.entrySet() ) {
    			properties.setProperty(entry.getKey(), entry.getValue());
    		}
    	}
	}

    private String createNewPropName(Configuration config, String name) {
    	if (StringUtils.isEmpty(espDatacenter))
    		return name;
    	String newPropName = espDatacenter + "." + name;
    	return (config.containsKey(newPropName) ? newPropName : name);
    }
    
	public String getProperty(String name) {
		return configuration.getString(this.createNewPropName(configuration, name));
    }

    public String getProperty(String name, String defaultValue) {
        return configuration.getString(this.createNewPropName(configuration, name), defaultValue);
    }

    public String[] getStringArray(String name) {
       return configuration.getStringArray(this.createNewPropName(configuration, name));
    }

    public boolean getBoolean(String key) {
        return configuration.getBoolean(this.createNewPropName(configuration, key));
    }

    public double getDouble(String key) {
        return configuration.getDouble(this.createNewPropName(configuration, key));
    }

    public float getFloat(String key) {
        return configuration.getFloat(this.createNewPropName(configuration, key));
    }

    public int getInt(String key) {
        return configuration.getInt(this.createNewPropName(configuration, key));
    }

    public long getLong(String key) {
        return configuration.getLong(this.createNewPropName(configuration, key));
    }

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getBoolean(java.lang.String, boolean)
	 */
	public boolean getBoolean(String name, boolean defaultValue) {
		return configuration.getBoolean(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getBoolean(java.lang.String, java.lang.Boolean)
	 */
	public Boolean getBoolean(String name, Boolean defaultValue) {
		return configuration.getBoolean(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getDouble(java.lang.String, double)
	 */
	public double getDouble(String name, double defaultValue) {
		return configuration.getDouble(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getDouble(java.lang.String, java.lang.Double)
	 */
	public Double getDouble(String name, Double defaultValue) {
		return configuration.getDouble(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getFloat(java.lang.String, float)
	 */
	public float getFloat(String name, float defaultValue) {
		return configuration.getFloat(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getFloat(java.lang.String, java.lang.Float)
	 */
	public Float getFloat(String name, Float defaultValue) {
		return configuration.getFloat(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getInt(java.lang.String, int)
	 */
	public int getInt(String name, int defaultValue) {
		return configuration.getInt(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getInteger(java.lang.String, java.lang.Integer)
	 */
	public Integer getInteger(String name, Integer defaultValue) {
		return configuration.getInteger(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getLong(java.lang.String, long)
	 */
	public long getLong(String name, long defaultValue) {
		return configuration.getLong(this.createNewPropName(configuration, name), defaultValue);
	}

	/* (non-Javadoc)
	 * @see com.comcast.core.framework.configuration.ConfigurationManager#getLong(java.lang.String, java.lang.Long)
	 */
	public Long getLong(String name, Long defaultValue) {
		return configuration.getLong(this.createNewPropName(configuration, name), defaultValue);
	}

	public Iterator<?> getKeys() {
		return configuration.getKeys();
	}

	public boolean addProperty(String key, Object value) {
		try
		{
			Configuration c = getSource(key, this.configuration);
			c.addProperty(key, value);
			return true;
		}
		catch (Exception e) {
			log.warn("Error adding property key: " + key + ", value = " + value, e);
		}
		return false;
	}

	public boolean setProperty(String key, Object value) {
		try
		{
			Configuration c = getSource(key, this.configuration);
			c.setProperty(key, value);
			return true;
		}
		catch (Exception e) {
			log.warn("Error setting property key: " + key + ", value = " + value, e);
		}
		return false;
	}

	private Configuration getSource(String key, Configuration config) {
		if (key == null)
			throw new IllegalArgumentException("Key must not be null");

		Configuration source;
		CompositeConfiguration cc = (CompositeConfiguration)config;
		int numConfigurations = cc.getNumberOfConfigurations();

		for (int i = 0; i < numConfigurations; i++) {
			source = cc.getConfiguration(i);
			if (source.containsKey(key))
				return source;
		}
		return config;
	}

	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}
	
	/*
	 * Returns true if the file has been modified since last load, false otherwise.
	 * This object maintains a map of all files loaded and the last modified time that
	 * is used to make a determination for reload during subsequent runs.
	 * 
	 * @return true if file has been modified since last reload. 
	 */
	//By pthakk200 : JIRA:TEAMT-445
	private boolean loadFile(File file){
		// If the argument is null OR does not denote a File type, return false for load flag.
		if (file == null || ! file.isFile())
			return false;

		// The resource is a file.
		if(! resourceMap.containsKey(file.getName())){ 
			// This is first time load for the file resource.
			if (log.isInfoEnabled())
				log.info("Initial load of configuration : " + file.getAbsolutePath());
			return true;
		} else { 
			// File has already been loaded once. Check last modified timestamp for any changes.
			if (resourceMap.get(file.getName()).longValue() != file.lastModified()) {
				if (log.isInfoEnabled())
					log.info("Configuration: " + file.getAbsolutePath() + " has been modified. This configuration will be reloaded.");
				return true;
			} else { // No change since last load.
				if (log.isInfoEnabled())
					log.info("Configuration: " + file.getAbsolutePath() + " has not been modified since last load. Skipping this configuration.");
				return false;
			}
		}
	}	
}
